use std::io;
// use std::sync::Arc;
use std::time::Duration;
use bytes::Bytes;
use pretty_hex::PrettyHex;
use tokio::net::TcpStream;
use crate::error::Error;
use tokio::{time};
use tokio::io::AsyncWriteExt;
// use tokio::sync::Notify;
use crate::cloud_pkt::{CloudPacket, CPPayload, ProtoCloudHeader, VehicleLoginReqPayload};
use crate::defs::{CloudCmdType, ResponseType};
use crate::util::fill_current_time;
use crate::configs::AgentConf;
use crate::upstream::cacher::MsgCache;

pub const LOGIN_TIMEOUT: u8 = 15;

pub struct Agent {
    pub login_sid: u16,
    pub client: Option<TcpStream>,
    pub msg_cache: MsgCache,
}

impl Agent {
    pub fn build(agent_cfg: &AgentConf) -> Self {
        Self {
            login_sid: 0,
            client: None,
            msg_cache: MsgCache::build(agent_cfg),
        }
    }

    ///
    /// 注册流程 Register
    ///
    pub async fn register(&mut self, agent_cfg: &AgentConf, try_cnt: u16, wait_now: bool) -> Result<(), Error> {
        let mut sleep_wait;
        self.login_sid = try_cnt;
        if try_cnt == 0 {
            sleep_wait = 0;
        } else if try_cnt < 3 {
            sleep_wait = 60;
        } else {
            sleep_wait = agent_cfg.login_timeout * 60;
        }
        if wait_now && sleep_wait == 0 {
            sleep_wait = 60;
        }
        log::info!("Agent will login after {} secs", sleep_wait);
        tokio::time::sleep(time::Duration::from_secs(sleep_wait as u64)).await;
        log::info!("Agent do login start");
        // TODO: Get timeout from storage
        let client = TcpStream::connect(agent_cfg.server_addr.as_str()).await;
        if client.is_err() {
            log::error!("could not connect to server -> {:?}", client.unwrap_err());
            return Err(Error::ConnectionFailed);
        }
        let mut client = client.unwrap();
        log::info!("Connect to TCP server done!\n");

        // 通知成功的notify
        // let reg_notify = Arc::new(Notify::new());
        // let notify_tx = reg_notify.clone();
        // let notify_rx = reg_notify.clone();

        // Step: 登陆
        let vin_num = agent_cfg.vin_num.as_bytes();
        let vin_num = <[u8; 17]>::try_from(vin_num).unwrap();
        let iccid = agent_cfg.iccid_num.as_bytes();
        let iccid_num = <[u8; 20]>::try_from(iccid).unwrap();

        let mut p_header = ProtoCloudHeader::new();
        p_header.cmd_id = CloudCmdType::VehicleLogin as u8;
        p_header.vin_id = vin_num;

        let p_login = VehicleLoginReqPayload {
            data_collect_time: fill_current_time(),//[24,11,25,17,15,50],
            login_sid: self.login_sid,
            sim_iccid: iccid_num,
            subsys_count: 0x01, // TODO: read from somewhere
            sussys_encoding_len: 0, // TODO: read from somewhere
            sys_encodings: vec![],
        };
        let mut p_pkt = CloudPacket {
            header: p_header,
            payload: Some(CPPayload::VLoginReq(p_login)),
            crc: 0,
            retry_cnt: 0,
        };
        let pkt_bytes = p_pkt.to_bytes();
        log::info!("Login request payload dump:\n{:?}", pkt_bytes.hex_dump());

        let wret = client.write_all(pkt_bytes.as_ref()).await;
        if wret.is_err() {
            log::warn!("Write login request failed\n");
            return Err(Error::ConnectionFailed);
        }
        let wret = client.flush().await;
        if wret.is_err() {
            log::warn!("Flush login request failed\n");
            return Err(Error::ConnectionFailed);
        }
        log::info!("Login write return {:?}", wret);

        let mut msg = vec![0; 1024];
        let mut up_interval = time::interval(Duration::from_secs(agent_cfg.login_timeout as u64));
        up_interval.tick().await;
        loop {
            tokio::select! {
                _ = up_interval.tick() => {
                    // 表示超时了
                    log::warn!("Timeout before get server login response\n");
                    return Err(Error::Timeout);
                }
                _ = client.readable() => {
                    // 即便readable()返回代表可读，但读取时仍然可能返回WouldBlock
                    match client.try_read(&mut msg) {
                        Ok(n) => {    // 成功读取了n个字节的数据
                            msg.truncate(n);
                            break;
                        }
                        Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
                            continue;
                        }
                        Err(e) => {
                            log::error!("TCP recv error {:?}\n", e);
                            return Err(Error::ConnectionFailed);
                        }
                    }
                }
            }
        }


        log::info!("GOT Server message dump:\n {:?}\n", msg.hex_dump());
        let msg_vec = msg.to_vec();
        let incoming_payload = Bytes::from(msg_vec);
        let server_resp = CloudPacket::try_from(incoming_payload);
        log::info!("Server response packet is {:?}", server_resp);
        if server_resp.is_err() {
            log::error!("Invalid server response {:?}", server_resp.unwrap_err());
            return Err(Error::InvalidPacket);
        }
        let server_pkt = server_resp.unwrap();
        if server_pkt.header.cmd_id != CloudCmdType::VehicleLogin.into() {
            log::error!("Not excepted packet");
            return Err(Error::InvalidPacket);
        }
        if server_pkt.header.resp_flag != ResponseType::OK.into() {
            log::error!("Vehicle login  ERROR");
            Err(Error::InvalidPacket)
        } else {
            log::info!("Vehicle login  OK");
            self.client = Some(client);
            Ok(())
        }

    }
}